﻿// 客户端TCP网络接收类，参考自互联网
// 协议模型：
// 长度（2字节）版本号（2字节）（数据）
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using System.Linq;
using Framework.Utilities;

namespace Framework.Core
{
    /// <summary>
    ///TCP Client 类，用于异步收发包
    ///做了并包处理
    ///使用 .net TcpClient 实现
    /// </summary>
    public class TCPClient : Singleton<TCPClient>
    {
        private Queue<byte[]> _sendQueue = new Queue<byte[]>();
        private Queue<byte[]> _recvQueue = new Queue<byte[]>();

        //长度值占2字节
        private readonly int LENGTH_CODE_LENGTH = 2;

        private readonly int MAX_BUFFER_SIZE = 65535;

        /// <summary>
        /// 预留版本号字节长度
        /// </summary>
        private readonly int VERSION_CODE_LENGTH = 0;

        public byte[] VersionCode = BitConverter.GetBytes((UInt16)0x0001);

        private byte[] _sendBuffer;
        private byte[] _recvBuffer;
        private int _nRecvBufferSize = 0;

        private Socket _socket = null;

        /// <summary>
        /// 异步读取数据线程。
        /// </summary>
        private Thread _receiveThread;

        /// <summary>
        /// 异步发送数据线程。
        /// </summary>
        private Thread _sendThread;

        /// <summary>
        /// 接收数据队列同步锁。
        /// </summary>
        private readonly object _recvQueueLocker = new object();

        /// <summary>
        /// 发送数据队列同步锁。
        /// </summary>
        private readonly object _sendQueueLocker = new object();

        /// <summary>
        /// 网络通信同步锁。
        /// </summary>
        private readonly object _tcpClientLocker = new object();

        /// <summary>
        /// 读取流数据量标记
        /// </summary>
        private Int32 bytesRead;

        private uint _ConnectChecker = 0;
        private readonly object _connectCheckerLocker = new object();
        private bool _asynSendSwitch = true;

        public Action OnNetworkDisconnected;

        public TCPClient()
        {
            _sendBuffer = new byte[MAX_BUFFER_SIZE];
            _recvBuffer = new byte[MAX_BUFFER_SIZE];

            if (_sendThread == null)
            {
                Debug.Log("init AsynSend");
                _sendThread = new Thread(new ThreadStart(AsynSend));
                _sendThread.IsBackground = true;
            }

            if (!_sendThread.IsAlive)
            {
                Debug.Log("Start AsynSend: " + _asynSendSwitch);
                _sendThread.Start();
            }
        }

        /// <summary>
        /// 提供给上层的发送函数。 仅将数据放入发送队列
        /// </summary>
        /// <param name="bytes"></param>
        public void Send(byte[] bytes)
        {
            lock (_sendQueueLocker)
                _sendQueue.Enqueue(bytes);
        }

        /// <summary>
        ///提供给上层的接受函数， 仅从接受队列获取一个数据包
        ///空队列返回 null
        /// </summary>
        /// <returns></returns>
        public byte[] Receive()
        {
            if (_recvQueue.Count > 0)
            {
                byte[] res;
                lock (_recvQueueLocker)
                    res = _recvQueue.Dequeue();
                return res;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 链接服务器。 失败会抛出字符串异常。
        /// </summary>
        /// <param name="IP"></param>
        /// <param name="Port"></param>
        public void Connect(string IP, int Port)
        {
            lock (_tcpClientLocker)
            {
                if ((_socket != null) && (_socket.Connected == true))
                {
                    throw new Exception("Exception. the tcpClient has Connectted.");
                }

                try
                {
                    Debug.Log("Connect.m_ConnectChecker: " + _ConnectChecker);
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
                    {
                        ReceiveBufferSize = MAX_BUFFER_SIZE,
                        NoDelay = true
                    };
                    _socket.Connect(IP, Port);
                    if (_socket != null)
                    {
                        if (_receiveThread == null)
                        {
                            _receiveThread = new Thread(new ThreadStart(DoReceive));
                            _receiveThread.IsBackground = true;
                        }

                        if (!_receiveThread.IsAlive)
                            _receiveThread.Start();
                    }
                }
                catch (Exception e)
                {
                    throw new Exception("Exception. the tcpClient do connecting error." + e);
                }
            }
        }

        /// <summary>
        /// 关闭链接
        /// </summary>
        public void Close()
        {
            lock (_connectCheckerLocker)
                _ConnectChecker++;
            if (_socket != null)
                _socket.Close();
            lock (_tcpClientLocker)
            {
                if ((_socket != null) && (_socket.Connected == true))
                    _socket.Close();
                _socket = null;
            }
            _receiveThread = null; //线程不用主动中断，只要保证内部没有执行，清除引用后会自动回收
            GC.Collect();
        }

        /// <summary>
        /// 强迫中断线程
        /// </summary>
        public void Release()
        {
            _asynSendSwitch = false; //关闭发送死循环
        }

        /// <summary>
        /// 判断是否处于连接状态
        /// </summary>
        /// <returns></returns>
        public bool Connected()
        {
            if ((_socket != null) && (_socket.Connected == true))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// 启动线程发送数据。
        /// </summary>
        private void AsynSend()
        {
            while (_asynSendSwitch)
            {
                DoSend();
                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// 每帧调用， 发送数据。
        /// 并包处理
        /// </summary>
        private void DoSend()
        {
            lock (_tcpClientLocker)
            {
                if ((_socket == null) || (_socket.Connected == false))
                {
                    return;
                }
            }
            int nTotalLength = 0;

            // 并包
            lock (_sendQueueLocker)
            {
                if (_sendQueue.Count == 0)
                {
                    return;
                }
                while ((nTotalLength < MAX_BUFFER_SIZE) && _sendQueue.Count > 0)
                {
                    byte[] packet = _sendQueue.Peek();
                    if (nTotalLength + VERSION_CODE_LENGTH + packet.Length + LENGTH_CODE_LENGTH < MAX_BUFFER_SIZE)
                    {
                        ushort ulength = (ushort)(packet.Length + VERSION_CODE_LENGTH);//+ LENGTH_CODE_LENGTH);

                        byte[] length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)ulength));
                        length.CopyTo(_sendBuffer, nTotalLength);
                        nTotalLength += LENGTH_CODE_LENGTH;
                        if (VERSION_CODE_LENGTH > 0)
                        {
                            VersionCode.CopyTo(_sendBuffer, nTotalLength);
                        }

                        nTotalLength += VERSION_CODE_LENGTH;
                        packet.CopyTo(_sendBuffer, nTotalLength);
                        nTotalLength += packet.Length;
                        _sendQueue.Dequeue();
                    }
                    else
                    {
                        break;
                    }
                }
            }
            // 真正发包到服务器
            try
            {
                _socket.Send(_sendBuffer, 0, nTotalLength, SocketFlags.None);
                // 清空发送缓存
                System.Array.Clear(_sendBuffer, 0, MAX_BUFFER_SIZE);
            }
            catch (Exception e)
            {
                Debug.Log("stream write error : " + e.ToString());
            }
        }

        private void DoReceive()
        {
            //读取流
            do
            {
                bytesRead = 0;
                try
                {
                    int size = MAX_BUFFER_SIZE - _nRecvBufferSize;
                    if (size > 0)
                    {
                        bytesRead = _socket.Receive(_recvBuffer, _nRecvBufferSize, size, SocketFlags.None);
                        _nRecvBufferSize += bytesRead;
                        if (bytesRead == 0)
                        {
                            //读的长度为0
                            bytesRead = 1;
                        }
                    }
                    else
                    {
                        bytesRead = 1; //缓存不够时继续循环，后面会对缓存数据进行处理
                        Debug.LogWarning("buffer not enough");
                    }
                }
                catch (ObjectDisposedException)
                {
                    // 网络流已被关闭，结束接收数据
                    Debug.LogError("tcp close");
                }
                catch (IOException ioex)
                {
                    //捕获WSACancelBlockingCall()导致的异常。
                    //原因：强迫终止一个在进行的阻塞调用。
                    //可直接捕获忽略，应该不会有不良影响。
                    //Debug.Error("WSACancelBlockCall");
                    Debug.LogException(ioex);
                }
                catch (Exception ex)
                {
                    Debug.LogException(ex);
                }
                //Debug.Log("DataReceive: " + bytesRead);
                SplitPackets();
            } while (bytesRead > 0);

            lock (_connectCheckerLocker)
                if (_ConnectChecker > 0)
                {
                    _ConnectChecker--;
                    Debug.Log("Disconnected.m_ConnectChecker: " + _ConnectChecker);
                }
                else
                {
                    Debug.LogError("receive bytes " + bytesRead);
                    if (OnNetworkDisconnected != null)
                    {
                        OnNetworkDisconnected();
                    }
                }

            Debug.Log("DataReceiveComplete");
        }

        /// <summary>
        /// 从RecvBuffer 中切分出多个Packets, 不足一个 Packet 的部分， 存留在 Buffer 中留待下次Split
        /// </summary>
        private void SplitPackets()
        {
            try
            {
                int offset = 0;
                while (_nRecvBufferSize > LENGTH_CODE_LENGTH + VERSION_CODE_LENGTH + offset)
                {
                    try
                    {
                        int nLength = System.Net.IPAddress.NetworkToHostOrder((short)
                            System.BitConverter.ToUInt16(_recvBuffer, offset));
                        offset += LENGTH_CODE_LENGTH;
                        if (_nRecvBufferSize >= nLength)
                        {
                            int realLength = nLength - VERSION_CODE_LENGTH;// - LENGTH_CODE_LENGTH
                            offset += VERSION_CODE_LENGTH;
                            byte[] packet = new byte[realLength];
                            Buffer.BlockCopy(_recvBuffer, offset, packet, 0, realLength);

                            lock (_recvQueueLocker) //此处理为独立线程处理，需加锁，否则会出现丢包
                            {
                                _recvQueue.Enqueue(packet);
                            }
                            _nRecvBufferSize -= nLength;
                            offset += realLength;
                        }
                        else
                        {
                            offset -= LENGTH_CODE_LENGTH; //调整偏移
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.LogException(ex);
                        break;
                    }
                }
                // 整理 RecvBuffer， 将buffer 内容前移
                Buffer.BlockCopy(_recvBuffer, offset, _recvBuffer, 0, _nRecvBufferSize);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
                Debug.LogError("SplitPackets error.");
                Close();
            }
        }
    }
}